package com.online.pay.common.sign;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;

/**
 * SignUtil是SignSDK的入口类，其主要包括对字符串的签名和验签两种操作，并同时支持xml和key=value两种格式的字符串。
 * <p>
 * SignSDK可以默认被ServerSDK读取，如果需要单独使用SignSDK，示例如下： <blockquote>
 * 
 * <pre>
 * SignUtil s = new SignUtil();
 * s.sign(str);
 * s.verify(str);
 * </pre>
 * 
 * </blockquote>
 * 
 * @author Pengdingqi wuhang
 *
 */
public class SignUtil {
	public static final String XML_TYPE = "xml";
	public static final String KEYVALUE_TYPE = "keyvalue";
	public static final String DEFAULT_ENCODE = "utf-8";

	/**
	 * 签名方法，将字符串转换为已签名的字符串
	 * 
	 * @param signStr
	 *            等待签名字符串
	 * @return String - 包含签名的完整字符串
	 */
	public String sign(String signStr) {
		if (StringUtils.isBlank(signStr)) {
			throw new RuntimeException("不支持空串签名");
		}
		// 1.判断 signStr 的类型
		String valueType = checkSignStr(signStr);
		// 2.签名处理
		try {
			if (valueType.equals(XML_TYPE)) {
				// 2.1 当signStr为xml处理
				signStr = StringUtils.replaceXMLBlank(signStr);
				String header = StringUtils.parseNodeValueFromXml("<Header>", "</Header>", signStr);
				String body = StringUtils.parseNodeValueFromXml("<Body>", "</Body>", signStr);
				StringBuffer sb = new StringBuffer();
				sb.append("<Header>").append(header).append("</Header>").append("|").append("<Body>").append(body)
				.append("</Body>");

				byte[] signByte;
				signByte = CertificateCoder.sign(sb.toString().getBytes(DEFAULT_ENCODE));
				String signReturn = Base64Util.encode(signByte);
				String cerId = CertificateCoder.getSignCerId();
				signStr = setSignXmlValue("CerID", cerId, signStr);
				signStr = setSignXmlValue("SignValue", signReturn, signStr);
			} else {
				// 2.2 当signStr为key=value&key=value。。。。。。。。。。。。处理
				byte[] signByte;
				signStr = signStr.trim();
				Map<String, String> reqDatamap = KeyValueUtil.keyValueStringToMap(signStr);
				reqDatamap.remove("CertID");
				reqDatamap.remove("SignValue");
				signStr = KeyValueUtil.mapToString(reqDatamap);
				signByte = CertificateCoder.sign(signStr.toString().getBytes(DEFAULT_ENCODE));
				String signReturn = Base64Util.encode(signByte);
				String cerId = CertificateCoder.getSignCerId();
				String appendStr = "&CertID=" + cerId + "&sign=" + signReturn;
				signStr = signStr + appendStr;
			}
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
		return signStr;
	}

	/**
	 * 验签方法，对已签名的字符串进行校验
	 * 
	 * @param checkValue
	 *            等待验证的字符串
	 * @return true表示验签成功， false表示验签失败
	 */
	public Boolean verify(String checkValue) {
		if (StringUtils.isBlank(checkValue)) {
			throw new RuntimeException("不支持空串验签");
		}
		// 1.判断 checkValue 的类型
		String valueType = checkSignStr(checkValue);
		// 2.验签处理
		if (valueType.equals(XML_TYPE)) {
			// 2.1 当checkValue为xml处理
			String signValue = StringUtils.parseNodeValueFromXml("<SignValue>", "</SignValue>", checkValue);
			String header = StringUtils.parseNodeValueFromXml("<Header>", "</Header>", checkValue);
			String body = StringUtils.parseNodeValueFromXml("<Body>", "</Body>", checkValue);
			StringBuffer sb = new StringBuffer();
			sb.append("<Header>").append(header).append("</Header>").append("|").append("<Body>").append(body)
			.append("</Body>");
			// 验签
			return signVerify(sb.toString(), signValue);
		} else {
			// 2.2 当checkValue为key=value&key=value。。。。。。。。。。。。处理
			checkValue = checkValue.trim();
			Map<String, String> checkDatamap = KeyValueUtil.keyValueStringToMap(checkValue);
			String signValue = checkDatamap.remove("SignValue");
			checkDatamap.remove("CertID");
			String s = KeyValueUtil.mapToString(checkDatamap);
			// 验签
			return signVerify(s, signValue);
		}
	}

	/**
	 * RSA签名
	 * @param content 待签名数据
	 * @param privateKey 商户私钥
	 * @param input_charset 编码格式
	 * @return 签名值
	 */
	public String sign(String content, String privateKey, String input_charset) {
		try {
			PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64Util.decode(privateKey) ); 
			KeyFactory keyf = KeyFactory.getInstance("RSA");
			PrivateKey priKey = keyf.generatePrivate(priPKCS8);
			Signature signature = Signature.getInstance("SHA1withRsa");
			signature.initSign(priKey);
			signature.update( content.getBytes(input_charset) );
			byte[] signed = signature.sign();
			return Base64Util.encode(signed);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 验签
	 * @param dataStr
	 * @param signValue
	 * @param publicKey
	 * @param input_charset
	 * @return
	 * @throws InvalidKeySpecException 
	 * @throws InvalidKeyException 
	 */
	public boolean verify(String dataStr,String signValue,String publicKey,String input_charset){
		boolean isValid = false;
		try {
			X509EncodedKeySpec  x509KeySpec = new X509EncodedKeySpec(Base64Util.decode(publicKey) ); 
			KeyFactory keyf = KeyFactory.getInstance("RSA");
			PublicKey publiKey = keyf.generatePublic(x509KeySpec);
			Signature signature = Signature.getInstance("SHA1withRsa");
			signature.initVerify(publiKey);
			signature.update(dataStr.getBytes(input_charset));
			isValid = signature.verify(Base64Util.decode(signValue));
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
		return isValid;
	}

	/**
	 * 验签方法
	 * 
	 * @param dataStr
	 * @param signValue
	 * 
	 */
	private boolean signVerify(String dataStr, String signValue) {
		boolean isValid = false;
		try {
			isValid = CertificateCoder.verify(dataStr.getBytes(DEFAULT_ENCODE), Base64Util.decode(signValue));
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
		return isValid;
	}

	/**
	 * @param str
	 * @return signStr的类型
	 */
	private String checkSignStr(String str) {
		str = str.trim();
		String valueType = null;
		if ((str.startsWith("<?xml") || str.startsWith("<GPay>")) && str.endsWith("</GPay>")) {
			valueType = XML_TYPE;
			//		} else if (str.matches("[a-zA-Z]+[=]+.*")) {

		} else {
			valueType = KEYVALUE_TYPE;
			//			throw new RuntimeException("暂时不支持非key=value与xml以外格式的字符串");
		}
		return valueType;
	}

	/**
	 * 
	 * @param noteName
	 * @param value
	 * @param signStr
	 * @return
	 */
	private String setSignXmlValue(String noteName, String value, String signStr) {
		String nodeStart = "<" + noteName + ">";
		String nodeEnd = "</" + noteName + ">";
		String shortNode = "<" + noteName + "/>";
		if (signStr.contains(nodeStart) && signStr.contains(nodeEnd)) {
			// 包含完整noteName节点

		} else if (signStr.contains(shortNode)) {
			// 包含缩略的空noteName节点
			// 将<noteName/> 格式的节点 替换成<noteName></noteName>
			signStr = signStr.replace(signStr, nodeStart + nodeEnd);
		} else {
			// 不存在node节点
			// 检查Sign节点是否存在
			if (signStr.contains("<Sign>") && signStr.contains("</Sign>")) {

			} else if (signStr.contains("<Sign/>")) {
				// 替换短节点
				signStr = signStr.replace("<Sign/>", "<Sign></Sign>");
			} else {
				// 插入空节点
				signStr = signStr.replace("</GPay>", "<Sign></Sign></GPay>");
			}
			String node = nodeStart + nodeEnd;
			signStr.replace("</Sign>", node + "</Sign>");
		}

		signStr = StringUtils.relaceNodeContent(nodeStart, nodeEnd, value, signStr);
		return signStr;
	}

}